//*********************************************************
//
//    Copyright (c) Microsoft. All rights reserved.
//    This code is licensed under the Apache License, Version 2.0.
//    THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF
//    ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY
//    IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR
//    PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.
//
//*********************************************************

namespace Microsoft.Research.ScientificWorkflow.TridentComposer
{
    using System;
    using System.Collections.ObjectModel;
    using System.IO;
    using Microsoft.Research.ScientificWorkflow.TridentModel;
    using Microsoft.Research.ScientificWorkflow.TridentUtilities;
    using SR = Microsoft.Research.DataLayer;
    using TridentAPI;

    /// <summary>
    /// Proxy class to create appdomain to save/open workflow from/to filesystem.
    /// </summary>
    public class FileSystemOperationsProxy : MarshalByRefObject
    {
        #region Private fields
        private SR.Connection registryConnection;
        private string tempDir;
        private FileSystemOperationsProxy proxyObject;
        private AppDomain childAppdomain;
        private bool initialized;
        private TridentWorkflowModel openedWorkflowModel;
        #endregion

        #region constructors
        /// <summary>
        /// Dummy constructor to allow creation of object through CreateInstanceAndUnwrap API.
        /// </summary>
        public FileSystemOperationsProxy()
        {
        }

        /// <summary>
        /// Initializes a new instance of the proxy.
        /// </summary>
        /// <param name="targetFileName">The filename of the xoml.</param>
        /// <param name="workflow">The workflow to be saved.</param>
        /// <param name="connection">The connnection.</param>
        public FileSystemOperationsProxy(SR.Connection connection)
        {
            #region Argument Validation

            if (connection == null)
            {
                throw new ArgumentNullException("connection");
            }
            #endregion

            this.tempDir = WorkflowComposer.CreateTempDirectory();
            this.registryConnection = connection;
            childAppdomain = this.CreateAppDomain();
            this.proxyObject = childAppdomain.CreateInstanceAndUnwrap(
                "Microsoft.Research.ScientificWorkflow.TridentComposer",
                "Microsoft.Research.ScientificWorkflow.TridentComposer.FileSystemOperationsProxy") as FileSystemOperationsProxy;
        }
        #endregion

        #region public methods
        /// <summary>
        /// Save the workflow to file system in a saperate appdomain..
        /// </summary>
        /// <param name="targetFileName">The name of the file where the workflow should be saved.</param>
        /// <param name="workflow">The workflow to be saved.</param>
        public void SaveToFile(string targetFileName, TridentWorkflowModel workflow)
        {
            #region Argument Validation
            if (string.IsNullOrEmpty(targetFileName))
            {
                throw new ArgumentNullException("targetFileName");
            }
            if (workflow == null)
            {
                throw new ArgumentNullException("workflow");
            }
            #endregion

            this.proxyObject.Init(this.registryConnection);
            this.proxyObject.SaveWorkflow(targetFileName, this.tempDir, workflow);
        }

        /// <summary>
        /// Open a workflow from filesystem.
        /// </summary>
        /// <param name="fileReferences">The references.</param>
        /// <returns>The workflow model.</returns>
        public TridentWorkflowModel OpenFromFile(Collection<FileReference> fileReferences)
        {
            this.proxyObject.Init(this.registryConnection);
            return this.proxyObject.CreateWorkflowModel(fileReferences);
        }

        /// <summary>
        /// Unload the appdomain.
        /// </summary>
        public void Unload()
        {
            try
            {
                UnloadAppDomain(this.childAppdomain);
                Directory.Delete(this.tempDir, true);
                this.openedWorkflowModel = null;
            }
            catch
            {

            }
        }

        #endregion

        #region private methods
        /// <summary>
        /// Save workflow to filesystem.
        /// </summary>
        /// <param name="targetFileName">The file name of the workflow.</param>
        /// <param name="tempDir">The temporary directory to download files.</param>
        /// <param name="workflow">The workflow</param>
        private void SaveWorkflow(string targetFileName, string tempDir, TridentWorkflowModel workflow)
        {
            if (workflow.IsBlackBox && this.openedWorkflowModel != null)
            {
                workflow.BlackBoxActivity = this.openedWorkflowModel.BlackBoxActivity;
            }

            ActivityComposer activityComposer = new ActivityComposer(this.registryConnection, false);
            WorkflowComposer composer = new WorkflowComposer(this.registryConnection, activityComposer);
            composer.SaveWorkflow(workflow, targetFileName, tempDir);
        }

        /// <summary>
        /// Create a workflow model.
        /// </summary>
        private TridentWorkflowModel CreateWorkflowModel(Collection<FileReference> fileReferences)
        {
            ActivityComposer activityComposer = new ActivityComposer(this.registryConnection, false);
            WorkflowComposer composer = new WorkflowComposer(this.registryConnection, activityComposer);
            this.openedWorkflowModel = composer.OpenWorkflowFromFileSystem(fileReferences);
            return this.openedWorkflowModel;
        }

        /// <summary>
        /// Initialize the proxy.
        /// </summary>
        /// <param name="targetFileName">The file name.</param>
        /// <param name="tempDir">The temp directory to download files to.</param>
        /// <param name="workflow">The workflow to save.</param>
        /// <param name="connection">The connection object.</param>
        private void Init(SR.Connection connection)
        {
            if (!initialized)
            {
                this.registryConnection = connection;
                SR.SR_Connection.Init();
                this.registryConnection.Open();
                this.initialized = true;

                TypeProviderHelper.Initialize(this.registryConnection);

                // Authenticate the user.
                try
                {
                    TridentAuthentication authenticate = new TridentAuthentication(this.registryConnection);
                    authenticate.AuthenticateUser();
                }
                catch (TridentCustomException)
                {
                    throw;
                }
            }
        }

        /// <summary>
        /// Creates a new app domain.
        /// </summary>
        /// <returns>New instance of the app domain.</returns>
        private AppDomain CreateAppDomain()
        {
            AppDomain newDomain = AppDomain.CreateDomain("FileSystemOperationsAppDomain");
            newDomain.UnhandledException += new UnhandledExceptionEventHandler(AppDomainUnhandledException);
            return newDomain;
        }

        /// <summary>
        /// Unloads the app domain.
        /// </summary>
        /// <param name="appDomain">Instance of the AppDomain.</param>
        private static void UnloadAppDomain(AppDomain appDomain)
        {
            if (appDomain != null)
            {
                AppDomain.Unload(appDomain);
            }
        }

        /// <summary>
        /// Method logs the unhandled exception in the app domain.
        /// </summary>
        /// <param name="sender">Object who raises the unhandled exception.</param>
        /// <param name="e">Event argument containing exception details.</param>
        private void AppDomainUnhandledException(object sender, UnhandledExceptionEventArgs e)
        {
            // No-op.
        }
        #endregion
    }
}
